package edit

import (
	"io"
	"os"; "fmt"
)
import "panel"

import "bufio"
import "strings"
import "strconv"
import "os/exec"
import "keys"
import "unicode"
import "edit/buffer"

type EditCompilePanel struct {
	panel.Common
	Edit *EditPanel
	mark panel.View
}

type MarkPanel struct {
	panel.Common
	marks map[int]string
}

func (e *MarkPanel) Clear() {
	e.marks = nil
}

func (e *MarkPanel) Mark(line int, message string) {
	if e.marks == nil {
		e.marks = map[int]string{}
	}
	e.marks[line-1] = message
}

func (e *MarkPanel) FocusForClick(xy panel.Point) panel.View {
	/*
	   if e.marks != nil && len(e.marks[line]) > 0 {
	       e.Show("err: " + e.marks[line] + ".")        
	   }
	   e.Cursor(line, col)
	*/
	return e
}

func (mp *MarkPanel) Refresh() {
	m := mp.marks
	if m == nil {
		return
	}
	/*
	   for line := 0; line < mp.Lines; line += 1 {
	       s := m[line] 
	       rune := ' '
	       if len(s) > 0 { rune = '>' }
	       mp.Put(line, 0, rune) 
	       // RESTORE if len(s) > 0 { curses.Stdwin.ChAttr( 2, line + 1, 10, 0, int(3) ) }
	   }
	*/
}

func (e *EditCompilePanel) Resize(wh panel.Size, offset panel.Point) {
	const delta = 5 * 256
	e.mark.Resize(panel.Size{delta, wh.Y}, offset)
	e.Edit.Resize(wh, offset.AddX(delta))
}

func (e *EditCompilePanel) At(xy panel.Point) panel.View {
	// panel.Log("EditCompilePanel: xy =", xy)
	if xy.X < 10*256 {
		return e.mark.At(xy)
	}
	// panel.Log("EditCompilePanel.At:", e.Edit)
	return e.Edit.At(xy.SubX(10 * 256))
}

func (e *EditCompilePanel) FocusForClick(xy panel.Point) panel.View {
	if xy.X < 10*256 {
		return e.mark.FocusForClick(xy)
	}
	return e.Edit.FocusForClick(xy.SubX(10 * 256))
}

func NewEditCompilePanel(di *panel.DrawInfo, target panel.Scrollable, other panel.View) *EditCompilePanel {
	mark := &MarkPanel{}
	edit := NewEditPanel(di, target, other, mark)
	result := &EditCompilePanel{panel.Common{}, edit, mark}
	edit.Parent(result)
	mark.Parent(result)
	return result
}

func (e *EditCompilePanel) Refresh() {
	// panel.Log("EditCompilePanel.Refresh")
	e.Edit.Refresh()
	// e.mark.Refresh()
}

// -----------------------------------------------------------------------------

type EditPanel struct {
	panel.Common                  // the bording stuff
	Draw         *panel.DrawInfo  // image & font drawing info
	target       panel.Scrollable // link to scroll-bar
	name         string           // filename associated with content
	other        panel.View       // panel for ^G
	Line, Col    int              // position within document
	scrollOffset int              // index of top line in window
	Lines, Cols  int              // size in character cells
	buffer       buffer.Buffer    // the content
}

func NewEditPanel(di *panel.DrawInfo, target panel.Scrollable, other panel.View, marks *MarkPanel) *EditPanel {
	// panel.Log("edit.NewEditPanel: di", di)
	return &EditPanel{panel.Common: panel.Common{},
		Draw:   di,
		target: target,
		other:  other,
		buffer: buffer.NewBuffer(),
	}
}

func (e *EditPanel) Buffer() buffer.Buffer {
	return e.buffer
}

func (e *EditPanel) SetBuffer(b buffer.Buffer) {
	e.buffer = b
}

func (e *EditPanel) Resize(wh panel.Size, offset panel.Point) {
	e.Common.Resize(wh, offset)
	e.Lines, e.Cols = e.Draw.Fix32ToCells(panel.Point(wh))
	panel.Log("EditPanel.Resize: wh", wh, "; lines & cols", e.Lines, e.Cols)
	panel.Log("                 off", offset)
}

func (e *EditPanel) Show(message string) {
	e.other.Show(message)
}

func (e *EditPanel) loadFile(fileName string) {
	panel.Log("loadFile '", fileName, "'")
	if fileName == "" {
		fileName = "/home/eh/Examples/edit.go"
	}
	e.name = fileName
	r, err := os.Open(fileName)
	if err != nil {
		panel.Log("BOOM -- ", err)
		return
	}
	defer r.Close()
	buffer := buffer.NewBuffer()
	br := bufio.NewReader(r)
	for {
		s, err := br.ReadString('\n')
		if err == io.EOF {
			break
		}
		buffer.AppendLine(s[0 : len(s)-1])
	}
	e.buffer = buffer
	e.Refresh()
}

func (e *EditPanel) saveFile(fileName string) {
	if fileName == "" {
		fileName = e.name
	} else {
		e.name = fileName
	}
	err := buffer.WriteBuffer(e.buffer, fileName)
	_ = err
}

func (e *EditPanel) Do(command string) {
	fun, arg := panel.Chop(command)
	if fun == "r" {
		e.loadFile(strings.TrimSpace(arg))
	} else if fun == "s" {
		e.saveFile(strings.TrimSpace(arg))
	} else {
		panel.Log("EditPanel.Do(", fun, " ", arg, ")")
	}
}

func (e *EditPanel) Cursor(xy panel.Point) {
	e.Common.Cursor(xy)
	line, col := e.Draw.Fix32ToCells(xy)
	panel.Log("Cursor: line, col = ", line, col, " from ", xy)
	e.Line, e.Col = line+e.scrollOffset, col
}

func (e *EditPanel) FocusForClick(xy panel.Point) panel.View {
	e.Cursor(xy)
	return e
}

func (e *EditPanel) damaged(line int) bool {
	return len(e.buffer.GetNotes(line)) > 0
	// if e.marks == nil { return false }
	// if e.marks.marks == nil { return false }
	// return e.marks.marks[line+e.scrollOffset] != ""
}

func (e *EditPanel) GainFocus(xy panel.Point) {
	panel.Log("We have the Focus", xy)
}

func (e *EditPanel) LoseFocus() {
	panel.Log("We lost the Focus")
	e.Floaty(panel.Point{}, "")
}

func (e *EditPanel) At(xy panel.Point) panel.View {
	// panel.Log("EditPanel.At")
	line, _ := e.Draw.Fix32ToCells(xy)
	dline := line + e.scrollOffset - 1
	notes := e.buffer.GetNotes(dline)
	if len(notes) > 0 {
		e.Floaty(xy, notes[0])
	} else {
		e.Floaty(xy, "")
	}
	return e
}

func (e *EditPanel) Refresh() {
	limit := e.buffer.Size()
	for i := 0; i < e.Lines; i += 1 {
		j := i + e.scrollOffset
		x := ""
		if j < limit {
			x = e.buffer.Get(j)
		}
		//
		e.refresh_text(i, 1, x)
		e.Clear(i, len(x))
	}
	// e.Cursor(e.Line - e.scrollOffset, e.Col)
}

var pens = map[string]string{
	"true":        "builtin",
	"false":       "keyword",
	"len":         "builtin",
	"iota":        "builtin",
	"close":       "builtin",
	"int":         "builtin",
	"string":      "builtin",
	"byte":        "builtin",
	"make":        "builtin",
	"nil":         "builtin",
	"new":         "builtin",
	"append":      "builtin",
	"copy":        "builtin",
	"panic":       "builtin",
	"recover":     "builtin",
	"cap":         "builtin",
	"complex":     "builtin",
	"real":        "builtin",
	"imag":        "builtin",
	"chan":        "keyword",
	"else":        "keyword",
	"fallthrough": "keyword",
	"goto":        "keyword",
	"select":      "keyword",
	"func":        "keyword",
	"return":      "keyword",
	"type":        "keyword",
	"interface":   "keyword",
	"continue":    "keyword",
	"break":       "keyword",
	"switch":      "keyword",
	"case":        "keyword",
	"default":     "keyword",
	"const":       "keyword",
	"var":         "keyword",
	"for":         "keyword",
	"if":          "keyword",
	"map":         "keyword",
	"struct":      "keyword",
	"defer":       "keyword",
	"package":     "keyword",
	"import":      "keyword",
	"range":       "keyword",
}

func (e *EditPanel) show(kind string, line, col int, start, end int, sheet panel.Paper, text string) {
	// panel.Log("show: ", kind, "; col ", col, ", start, end = ", start, ", ", end, "; text '", text, "'len(", len(text), ")")
	xy := e.Draw.CellsToFix32(line, col+start)
	chunk := text[start:end]
	pen := e.Draw.Pens[pens[chunk]]
	if len(chunk) > 1 && chunk[0] == '/' && chunk[1] == '/' {
		pen = e.Draw.Pens["comment"]
	}
	if len(chunk) > 1 && chunk[0] == '"' {
		pen = e.Draw.Pens["string"]
	}
	if pen == nil {
		pen = e.Draw.Thingy
	}
	e.PutString(xy, sheet, pen, chunk)
	// panel.Log("show: '", chunk, "'")
}

const (
	START = iota
	NAME
	SLASH
	COMMENT
	ESCAPE
	STRING
	OTHER
)

func (e *EditPanel) refresh_text(line, col int, text string) {
	// panel.Log("refresh_text: '", text, "'")
	// panel.Log("e.Cols ", e.Cols)
	if len(text) >= e.Cols {
		text = text[:e.Cols]
	}
	start := 0
	quote := rune(0)
	state := START
	sheet := e.Draw.Back
	if e.damaged(line) {
		sheet = panel.RedPaper
		panel.Log("line", line, "damaged")
	}
	for ti, ch := range text {
		switch state {
		case START:
			start = ti
			if unicode.IsLetter(ch) {
				state = NAME
			} else if ch == '/' {
				state = SLASH
			} else if ch == '"' || ch == '\'' {
				state = STRING
				quote = ch
			} else {
				state = OTHER
			}

		case NAME:
			if unicode.IsLetter(ch) {
			} else {
				e.show("NAME", line, col, start, ti, sheet, text)
				start = ti
				state = OTHER
			}

		case ESCAPE:
			state = STRING

		case STRING:
			if ch == quote {
				e.show("STRING", line, col, start, ti+1, sheet, text)
				start = ti + 1
				state = START
			} else if ch == '\\' {
				state = ESCAPE
			}

		case COMMENT:
			// until EOL

		case SLASH:
			if ch == '/' {
				state = COMMENT
			} else if unicode.IsLetter(ch) {
				e.show("OTHER", line, col, start, ti, sheet, text)
				start = ti
				state = NAME
			} else {
				state = OTHER
			}

		case OTHER:
			if unicode.IsLetter(ch) {
				e.show("OTHER", line, col, start, ti, sheet, text)
				start = ti
				state = NAME
			} else if ch == '"' {
				e.show("OTHER", line, col, start, ti, sheet, text)
				start = ti
				state = STRING
			}

		}
	}
	e.show("END", line, col, start, len(text), sheet, text)
}

func (e *EditPanel) Clear(line, col int) {
	for i := col + 1; i < e.Cols+1; i += 1 {
		xy := e.Draw.CellsToFix32(line, i)
		e.Put(xy, e.Draw.Back, e.Draw.Thingy, ' ')
	}
}

func (e *EditPanel) Key(rune int) panel.View {
	// panel.Log("EditPanel.Key(", rune, ")")
	// panel.Log("e.Line, e.Col:", e.Line, e.Col)
	needsScrolling := false
	switch rune {

	case keys.XK_Home:
		if e.Line == 0 {
			e.Line = e.buffer.Size()
			e.scrollOffset = e.Line - e.Lines
			if e.scrollOffset < 0 {
				e.scrollOffset = 0
			}
		} else {
			e.Line = 0
			e.scrollOffset = 0
		}
		e.Col = 0
		e.Refresh()

	case keys.XK_End:
		if e.Col == 0 {
			e.Col = len(e.buffer.Get(e.Line)) - 1
		} else {
			e.Col = 0
		}

	case keys.XK_Up:
		if e.Line > e.scrollOffset {
			e.Line -= 1
		} else if e.scrollOffset > 0 {
			e.scrollOffset -= 1
			e.Line -= 1
			e.Refresh()
			needsScrolling = true
		} else {
		}

	case keys.XK_Down:
		if e.Line-e.scrollOffset < e.Lines-1 {
			e.Line += 1
		} else {
			e.scrollOffset += 1
			e.Line += 1
			e.Refresh()
			needsScrolling = true
		}

	case keys.XK_Left:
		if e.Col > 0 {
			e.Col -= 1
		}

	case keys.XK_Right:
		if e.Col < e.Cols-1 {
			e.Col += 1
		}

	case keys.XK_Page_Up:
		if e.Line == e.scrollOffset {
			e.Line -= e.Lines
			e.scrollOffset -= e.Lines
			if e.Line < 0 {
				e.Line, e.scrollOffset = 0, 0
			}
			e.Refresh()
			needsScrolling = true
		} else {
			e.Line = e.scrollOffset
			needsScrolling = true
		}

	case keys.XK_Page_Down:
		if e.Line-e.scrollOffset == e.Lines-1 {
			// next screen
			e.Line += e.Lines
			e.scrollOffset += e.Lines
			e.Refresh()
			needsScrolling = true
		} else {
			// to bottom of screen
			e.Line = e.Lines + e.scrollOffset - 1
		}

	case keys.XK_Delete:
		e.buffer.DeleteRune(e.Line, e.Col+1)
		line := e.buffer.Get(e.Line)
		e.refresh_text(e.Line-e.scrollOffset, 1, line)
		e.Clear(e.Line-e.scrollOffset, len(line)+1)

	case keys.XK_BackSpace:
		if e.Col > 0 {
			e.buffer.DeleteRune(e.Line, e.Col)
			line := e.buffer.Get(e.Line)
			e.refresh_text(e.Line-e.scrollOffset, 1, line)
			e.Clear(e.Line-e.scrollOffset, len(line)+1)
			e.Col -= 1
		}

	case 'l' - 'a' + 1:
		e.buffer.DeleteLine(e.Line)
		e.Refresh()

	case 'j' - 'a' + 1:
		e.buffer.Join(e.Line)
		e.Refresh()

	case keys.XK_Return:
		e.buffer.Split(e.Line, e.Col)
		limit := panel.Min(e.buffer.Size(), e.Lines)
		for line := e.Line; line < limit; line += 1 {
			e.refresh_text(line-e.scrollOffset, 1, e.buffer.Get(line))
			e.Clear(line-e.scrollOffset, len(e.buffer.Get(line)))
		}
		e.Line += 1
		e.Col = 0
		// TODO scrolling issues

	case 'd' - 'a' + 1:
		reports := e.compile()
		e.buffer.DiscardNotes()
		for _, report := range reports {
			ln := report.LineNumber
			iln, _ := strconv.Atoi(ln)
			// e.marks.Mark(iln, report.MessageText)
			e.buffer.AddNote(iln-1, report.MessageText)
		}
		e.Refresh()

	case keys.XK_F1:
		if e.other != nil {
			return e.other
		}

	default:
		if ' ' <= rune && rune <= 126 {
			e.buffer.Insert(rune, e.Line, e.Col)
			e.Col += 1
			e.refresh_text(e.Line-e.scrollOffset, 1, e.buffer.Get(e.Line))
		}
	}
	if needsScrolling && e.target != nil {
		// e.target.FillIn(e.Lines, len(e.Content), e.scrollOffset) 
	}
	xy := e.Draw.CellsToFix32(e.Line-e.scrollOffset, e.Col)
	// panel.Log("EditPanel.Key: line, col:", e.Line, e.Col, "cursor :=", xy)
	e.Cursor(xy)
	return nil
}

type Report struct {
	FileName    string
	LineNumber  string
	MessageText string
}

func (e *EditPanel) compile() []Report {
	const cmd = "6g"
	var CMD = os.Getenv("GOBIN") + "/" + "go"
	panel.Log("doing 'compile' with 6g")
	const fname = "/tmp/compiling.go"
	err := buffer.WriteBuffer(e.buffer, fname)
	do := exec.Command(CMD, "tool", "6g", fname)
	bytes, err := do.CombinedOutput()
	// if err != nil { panel.Log("oops: ", err); return nil }
	_ = err
	panel.Log("output: ", string(bytes))
	//
	reports := []Report{}
	lines := strings.Split(string(bytes), "\n")
	e.buffer.DiscardNotes()
	for _, line := range lines[0:1] {
		if len(line) > 0 && line[0] != '\t' {
            fmt.Println("Message", line)
			colon := strings.IndexRune(line, ':')
			space := strings.IndexRune(line, ' ')
			fileName := line[0:colon]
			lineNumber := line[colon+1 : space-1]
			messageText := line[space+1:]
			reports = append(reports, Report{fileName, lineNumber, messageText})
		}
	}
	return reports
}
